

package com.example.redis;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Redis工具类
 *
 * @author Mark sunlightcs@gmail.com
 * @since 1.0.0
 * /Users/yyyyjinying/demo-file/git/backend/project-company/qar-cloud/qar-commons/qar-commons-tools/src/main/java/io/qar/commons/tools/redis/RedisUtils.java
 */
@Slf4j
@Component
public class RedisUtils {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 默认过期时长为24小时，单位：秒
     */
    public final static long DEFAULT_EXPIRE = 60 * 60 * 24L;
    /**
     * 过期时长为1小时，单位：秒
     */
    public final static long HOUR_ONE_EXPIRE = 60 * 24 * 60 * 1L;
    /**
     * 过期时长为6小时，单位：秒
     */
    public final static long HOUR_SIX_EXPIRE = 60 * 60 * 6L;
    /**
     * 不设置过期时长
     */
    public final static long NOT_EXPIRE = -1L;

    /**
     * 原子性地从Redis列表中弹出固定数量的元素。
     *
     * @param key   列表键名
     * @param count 要弹出的元素数量
     * @return 弹出的元素列表
     */
    public List<String> rightPopCount(String key, int count) {
        List<Object> objects = redisTemplate.opsForList().rightPop(key, count);
        if (objects == null) {
            return null;
        }
        return objects.stream().map(String::valueOf).collect(Collectors.toList());
    }

    public void set(String key, Object value, long expire) {
        redisTemplate.opsForValue().set(key, value);
        if (expire != NOT_EXPIRE) {
            expire(key, expire);
        }
    }

    public boolean setIfNotExists(String key, Object value, long expire) {
        boolean res = redisTemplate.opsForValue().setIfAbsent(key, value);
        if (res && expire != NOT_EXPIRE) {
            expire(key, expire);
        }
        return res;
    }

    public void set(String key, Object value) {
        set(key, value, DEFAULT_EXPIRE);
    }

    public boolean setIfNotExists(String key, Object value) {
        return setIfNotExists(key, value, DEFAULT_EXPIRE);
    }

    public Object get(String key, long expire) {
        Object value = redisTemplate.opsForValue().get(key);
        if (expire != NOT_EXPIRE) {
            expire(key, expire);
        }
        return value;
    }

    public Object get(String key) {
        return get(key, NOT_EXPIRE);
    }

    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    public List<Object> mGet(Set<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    public void deleteByPattern(String pattern) {
        redisTemplate.delete(keys(pattern));
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    public void delete(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 删除匹配的keys
     * GZ_DECODE:123
     * GZ_DECODE:193.45.62.345:123
     * 删除满足所有
     *
     * @param prevKey "GZ_DECODE:*"
     */
    public void deleteScanKeys(String prevKey) {
        List<String> keys = scanKeys(prevKey);
        for (String key : keys) {
            delete(key);
        }
    }

    public List<String> scanKeys(String pattern) {
        List<String> resultList = new ArrayList<>();
        try {
//            // 使用ScanOptions来配置SCAN命令的行为，如匹配模式和每次迭代返回的键的数量
            ScanOptions options = ScanOptions.scanOptions()
                    .match(pattern)
                    .count(100) // 可以根据需要调整每次迭代的数量
                    .build();


            redisTemplate.execute((RedisCallback<Void>) connection -> {
                Cursor<byte[]> rawCursor = connection.scan(options);
                while (rawCursor.hasNext()) {
                    byte[] bytes = rawCursor.next();
                    resultList.add(new String(bytes, StandardCharsets.UTF_8));
                }
                rawCursor.close(); // 关闭游标以释放资源
                return null; // RedisCallback要求返回一个非null值，这里返回null是因为我们主要关注副作用（即填充resultList）
            });
        } catch (Exception e) {
            // 异常处理
            e.printStackTrace();
        }
        return resultList;
    }

    public Map<String, Object> hGetAll(String key) {
        HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
        return hashOperations.entries(key);
    }

    public void hMSet(String key, Map<String, Object> map) {
        hMSet(key, map, DEFAULT_EXPIRE);
    }

    public void hMSet(String key, Map<String, Object> map, long expire) {
        redisTemplate.opsForHash().putAll(key, map);

        if (expire != NOT_EXPIRE) {
            expire(key, expire);
        }
    }

    public void hSet(String key, String field, Object value) {
        hSet(key, field, value, DEFAULT_EXPIRE);
    }

    public void hSet(String key, String field, Object value, long expire) {
        redisTemplate.opsForHash().put(key, field, value);

        if (expire != NOT_EXPIRE) {
            expire(key, expire);
        }
    }

    public void expire(String key, long expire) {
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }

    public void hDel(String key, Object... fields) {
        redisTemplate.opsForHash().delete(key, fields);
    }

    public void leftPush(String key, Object value) {
        leftPush(key, value, DEFAULT_EXPIRE);
    }


    /**
     * 批量lpush
     */
    public long leftPushAll(String key, Collection<String> values) {
        return redisTemplate.opsForList().leftPushAll(key, values);
    }

    /**
     * 移除列表中与指定值相等的元素。
     *
     * @param key   列表键名
     * @param count 要移除的数量。如果为正数，则从前向后移除；如果为负数，则从后向前移除；如果为0，则移除所有匹配项。
     * @param value 要移除的值
     * @return 被移除的元素数量
     */
    public long lRem(String key, long count, String value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }

    public void lRem(String key, long count, List<String> values) {
        for (String value : values) {
            redisTemplate.opsForList().remove(key, count, value);
        }
    }

    /**
     * 批量插入相同的值
     *
     * @param key
     * @param num
     * @param val
     */
    public void nCopiesLetfPush(String key, Integer num, String val) {
        List<String> list = new ArrayList<>(Collections.nCopies(num, val));
        for (String s : list) {
            leftPush(key, s);
        }
    }

    public void leftPush(String key, Object value, long expire) {
        redisTemplate.opsForList().leftPush(key, value);

        if (expire != NOT_EXPIRE) {
            expire(key, expire);
        }
    }

    public void leftPushByPipelined(String key, List<String> value, long expire) {
        redisTemplate.executePipelined((RedisCallback<Object>) connect -> {
            for (String str : value) {
                connect.listCommands().lPush(key.getBytes(StandardCharsets.UTF_8), str.getBytes(StandardCharsets.UTF_8));
            }
            return null;
        });

        if (expire != NOT_EXPIRE) {
            expire(key, expire);
        }
    }

    public void getList(byte[] val) {
//        String sseMessage = JsonUtils.parseDefaultTypingObject(new String(val, StandardCharsets.UTF_8), String.class);
//        log.info("stoptask  {}", sseMessage);
        String json = new String(val, StandardCharsets.UTF_8);
        json = json.trim(); // 去除前导和尾随空格
        ObjectMapper mapper = new ObjectMapper();
        try {
            String s = mapper.readValue(json, String.class);
            log.info("stoptask  {}", s);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public byte[] serializeList(List<Object> list) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            for (Object obj : list) {
                oos.writeObject(obj);
            }
            oos.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    public Object rightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    public Boolean setIfAbsent(String key, Object value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    public Boolean setIfAbsent(String key, Object value, long expire, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, expire, timeUnit);
    }

    public Long execute(DefaultRedisScript<Long> script, List<String> keys, Object... args) {
        return redisTemplate.execute(script, keys, args);
    }


    public Boolean hasKey(String codeKey) {
        Object o = redisTemplate.opsForValue().get(codeKey);
        return o != null;
    }

    /**
     * 增加手机号验证码发送次数，首次设置时添加过期时间
     */
    public Boolean incrementSendCount(String countKey, Integer num, long expire) {
        Integer sendCount = (Integer) get(countKey);
        if (sendCount == null) {
            set(countKey, 1, expire);
        } else if (sendCount < num) {
            // 次数未达上限，递增
            redisTemplate.opsForValue().increment(countKey, 1);
            return false;
        } else {
            return true;
        }
        return false;
    }
}